﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using JsonRpcGen.ParameterHandler;
using JsonRpcGen.ReturnHandler;
using JsonRpcGen.TypeHandler;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Schema;

namespace JsonRpcGen
{
    class Program
    {

        static void Main(string[] args)
        {
#if DEBUG
            args=new string[]{"http://localhost:85/jsonrpc","xbmc","xbmc", "XBMCRPC"};
#endif
            
            Global.TargetDir = new DirectoryInfo(args[3]);
            Global.BaseNamespace = args[3];

            var schemaTask = GetSchema(args[0], args[1], args[2]);
            schemaTask.Wait();
            var schema=schemaTask.Result;

            var types=schema["result"]["types"];
            var methods=schema["result"]["methods"];
            var notifications = schema["result"]["notifications"];

            var replacements = GetReplacements(types);

            var multiInheritanceInterfaces = GenTypes(types, replacements);
            string notificationInvoker;
            var methodClasses= GenMethods( methods, notifications,replacements, out notificationInvoker);
            CopyBaseClases( methodClasses, multiInheritanceInterfaces, notificationInvoker);
        }

        private static void CopyBaseClases(List<string> methodClasses, List<string> inheritenceConverter, string notificationInvoker)
        {
            File.Copy("Templates\\ClientXBMC.templ", Path.Combine(Global.TargetDir.FullName, "ClientXBMC.cs"),true);
            File.Copy("Templates\\ConnectionSettings.templ", Path.Combine(Global.TargetDir.FullName, "ConnectionSettings.cs"), true);
            
            var client=File.ReadAllText("Templates\\Client.templ");
            var methodsPropertiesList = methodClasses.Select(m => "        public Methods." + m + " " + m + " { get; private set; }");
            var methodProperties = string.Join(Environment.NewLine, methodsPropertiesList);
            client=client.Replace("%json_methods_properties%", methodProperties);
            var methodsInitList = methodClasses.Select(m => "           " + m + " = new Methods." + m + "(this);");
            var methodInits = string.Join(Environment.NewLine, methodsInitList);
            client = client.Replace("%json_methods_init%", methodInits);
            var converterList = inheritenceConverter.Select(i=> i.Split('.').Last()).Select(i => "           Serializer.Converters.Add(new " + i + "Converter(MultipleInheritanceKey" + i + "));");
            var converter = string.Join(Environment.NewLine, converterList);
            client = client.Replace("%json_converter%", converter);
            client = client.Replace("%notification_invoker%", notificationInvoker);
            File.WriteAllText(Path.Combine(Global.TargetDir.FullName, "Client.cs"), client);
        }

        async static internal Task<JObject> GetSchema(string address, string user, string password)
        {
            var request = WebRequest.Create(address);
            request.Credentials = new NetworkCredential(user, password);
            request.ContentType = "application/json";
            request.Method = "POST";
            var postStream = await request.GetRequestStreamAsync();

            var jsonPost = new JObject { new JProperty("jsonrpc", "2.0"), new JProperty("method", "JSONRPC.Introspect"), new JProperty("id", 1) };
            var jsonRequest = jsonPost.ToString();
            byte[] postData = Encoding.UTF8.GetBytes(jsonRequest);
            postStream.Write(postData, 0, postData.Length);
            postStream.Dispose();

            var response = await request.GetResponseAsync();
            var responseStream = response.GetResponseStream();
            string responseData = null;
            if (responseStream != null)
            {
                var streamReader = new StreamReader(responseStream);
                responseData = streamReader.ReadToEnd();
                responseStream.Dispose();
                streamReader.Dispose();
            }

            response.Close();

            JObject query = JObject.Parse(responseData);
            return query;
        }

        private static Dictionary<string, string> GetReplacements(JToken types)
        {
            var replacements = new Dictionary<string, string>();
            foreach (JProperty type in types)
            {
                var typeField = type.First["type"];
                if (typeField != null)
                {
                    var typeName = typeField.ToString();
                    switch (typeName)
                    {
                        case "integer":
                            replacements.Add(type.Name, "int");
                            break;
                        case "number":
                            replacements.Add(type.Name, "double");
                            break;
                    }
                }
            }
            replacements.Add("PVR.ChannelGroup.Id", "int");
            replacements.Add("Notifications.Library.Audio.Type", "string");
            replacements.Add("Notifications.Library.Video.Type", "string");
            return replacements;
        }

        private static List<string> GenNotifications(JToken notifications, Dictionary<string, string> replacements)
        {
            throw new NotImplementedException();
        }

        private static List<string> GenMethods(JToken methods,JToken notifications, Dictionary<string, string> replacements, out string notificationInvoke)
        {
            notificationInvoke = null;
            var path = Path.Combine(Global.TargetDir.FullName, "Methods");
            var methodDir = new DirectoryInfo(path);
            if (methodDir.Exists)
            {
                methodDir.Delete(true);
            }
            methodDir.Create();
            var writer3 = new StringWriter();
            
            foreach (JProperty method in methods)
            {
                var parts = method.Name.Split('.');
                var namesp = Global.BaseNamespace + ".Methods";
                var className = parts[0];
                var methodName = parts[1];
                var classFile = new FileInfo(Path.Combine(path, className + ".cs")); 
                if (!classFile.Exists)
                {
                    var writer = new StreamWriter(classFile.Open(FileMode.Create, FileAccess.Write));
                    writer.WriteLine("using System;");
                    writer.WriteLine("using System.Threading.Tasks;");
                    writer.WriteLine("using Newtonsoft.Json.Linq;");
                    writer.WriteLine("namespace " + namesp);
                    writer.WriteLine("{");
                    writer.WriteLine("   public partial class " + className);
                    writer.WriteLine("   {");
                    writer.WriteLine("        private readonly Client _client;");
                    writer.WriteLine("          public " + className + "(Client client)");
                    writer.WriteLine("          {");
                    writer.WriteLine("              _client = client;");
                    writer.WriteLine("          }");
                    writer.Close();
                }

                var retType = GetReturnHandler(method, replacements);
                var parameters = GetParams(method, replacements, methodName);
                for (int i = 0; i < parameters.GetOverloadCount(); i++)
                {
                    parameters.SetOverload(i);
                    retType.SetOverload(i);
                    var writer2 = new StreamWriter(classFile.Open(FileMode.Append, FileAccess.Write));
                    retType.WriteType(writer2, replacements);
                    parameters.WriteTypes(writer2, replacements);
                    writer2.WriteLine();
                    writer2.Write("        async public Task<");
                    writer2.Write(retType.Name);
                    writer2.Write("> ");
                    writer2.Write(methodName);
                    if (i > 0)
                    {
                        writer2.Write("2");
                    }
                    writer2.Write("(");
                    writer2.Write(parameters.GetParamList());
                    writer2.Write("");
                    writer2.WriteLine(")");
                    writer2.WriteLine("        {");
                    writer2.WriteLine("            var jArgs = new JObject();");
                    foreach (var pname in parameters.GetNames())
                    {
                        writer2.WriteLine("             if (" + pname.Name + " != null)");
                        writer2.WriteLine("             {");
                        writer2.WriteLine("                 var jprop" + pname.Name + " = JToken.FromObject(" + pname.Name + ", _client.Serializer);");
                        writer2.WriteLine("                 jArgs.Add(new JProperty(\"" + pname.OriginalName + "\", jprop" + pname.Name +"));");
                        writer2.WriteLine("             }");
                    }
                    writer2.WriteLine("            var jRet = await _client.GetData<" + retType.Name+">(\""+ method.Name + "\", jArgs);");
                    writer2.WriteLine("            return jRet;");
                    writer2.WriteLine("        }");

                    writer2.Close();                    
                }

                

            }
            foreach (JProperty notification in notifications)
            {
                var parts = notification.Name.Split('.');
                var className = parts[0];
                var methodName = parts[1];
                var classFile = new FileInfo(Path.Combine(path, className + ".cs"));

                var parameters = GetNotifyParams(notification, replacements, methodName);
                //var retType = GetReturnHandler(notification, replacements);
                for (int i = 0; i < parameters.GetOverloadCount(); i++)
                {
                    parameters.SetOverload(i);
                    //retType.SetOverload(i);
                    var writer2 = new StreamWriter(classFile.Open(FileMode.Append, FileAccess.Write));
                    //retType.WriteType(writer2, replacements);
                    parameters.WriteTypes(writer2, replacements);
                    writer2.WriteLine();
                    writer2.Write("        public delegate void ");
                    writer2.Write(methodName);
                    if (i > 0)
                    {
                        writer2.Write("2");
                    }
                    writer2.Write("Delegate(");
                    writer2.Write(parameters.GetParamList());
                    writer2.Write("");
                    writer2.WriteLine(");");
                    writer2.Write("        public event ");
                    writer2.Write(methodName);
                    if (i > 0)
                    {
                        writer2.Write("2");
                    }
                    writer2.Write("Delegate ");
                    writer2.Write(methodName);
                    if (i > 0)
                    {
                        writer2.Write("2");
                    }
                    writer2.WriteLine(";");
                    writer2.Write("        internal void Raise");
                    writer2.Write(methodName);
                    if (i > 0)
                    {
                        writer2.Write("2");
                    }
                    writer2.Write("(");
                    writer2.Write(parameters.GetParamList());
                    writer2.Write("");
                    writer2.WriteLine(")");
                    writer2.WriteLine("        {");
                    writer2.Write("            if (");
                    writer2.Write(methodName);
                    if (i > 0)
                    {
                        writer2.Write("2");
                    }
                    writer2.WriteLine(" != null)");
                    writer2.WriteLine("            {");
                    writer2.Write("                ");
                    writer2.Write(methodName);
                    if (i > 0)
                    {
                        writer2.Write("2");
                    }
                    writer2.Write(".BeginInvoke(");
                    writer2.Write(string.Join(", ",parameters.GetNames().Select(p=>p.Name)));
                    writer2.WriteLine(", null, null);");
                    writer2.WriteLine("            }");
                    writer2.WriteLine("        }");

                    writer2.Close();     

                    writer3.Write("        case \"");
                    writer3.Write(className);
                    writer3.Write(".");
                    writer3.Write(methodName);
                    writer3.WriteLine("\":");
                    
                    
                    
                    writer3.Write("            ");
                    writer3.Write(className);
                    writer3.Write(".Raise");
                    writer3.Write(methodName);
                    writer3.WriteLine("(");
                    var first = true;
                    foreach (var paramHandler in parameters.GetNames())
                    {
                        if (first)
                        {
                            first = false;
                        }
                        else
                        {
                            writer3.Write(", ");    
                        }
                        writer3.Write("jObject[\"params\"][\"");
                        writer3.Write(paramHandler.OriginalName);
                        writer3.Write("\"].ToObject<");
                        if (paramHandler.Type.Contains(methodName))
                        {
                            writer3.Write("Methods.");
                            writer3.Write(className);
                            writer3.Write(".");
                        }
                        writer3.Write(paramHandler.Type);
                        writer3.WriteLine(">(Serializer)");
                    }
                    writer3.WriteLine(");");
                    writer3.WriteLine("            break;");
                }
            }
            
            foreach (var classFile in methodDir.GetFiles())
            {
                var writer = new StreamWriter(classFile.Open(FileMode.Append, FileAccess.Write));
                writer.WriteLine("   }");
                writer.WriteLine("}");
                writer.Close();                
            }
            notificationInvoke = writer3.ToString();
            return methodDir.GetFiles().Select(f=>Path.GetFileNameWithoutExtension(f.Name)).ToList();
        }

        private static ParamsHandler GetParams(JProperty method, Dictionary<string, string> replacements, string methodName)
        {
            var parameters=method.First["params"];
            var handler = new ParamsHandler(parameters, replacements, methodName, "");
            return handler;
        }
        private static ParamsHandler GetNotifyParams(JProperty method, Dictionary<string, string> replacements, string methodName)
        {
            var parameters=method.First["params"];
            var handler = new ParamsHandler(parameters, replacements, methodName, methodName);
            return handler;
        }

        private static ReturnHandler.ReturnHandler GetReturnHandler(JProperty method, Dictionary<string, string> replacements)
        {
            
            var type = method.First["returns"]["type"];
            if(type!=null)
            {
                switch (type.ToString())
                {
                    case "boolean":
                        return new ReturnHandler.ReturnHandler("bool");
                    case "integer":
                        return new ReturnHandler.ReturnHandler("int");
                    case "number":
                        return new ReturnHandler.ReturnHandler("double");
                    case "string":
                        return new ReturnHandler.ReturnHandler("string");
                    case "object":
                        return new ObjectReturnHandler(method);
                    case "any":
                        return new ReturnHandler.ReturnHandler("JObject");
                    case "array":
                        return new ArrayReturnHandler(method);
                    default:
                        throw new NotImplementedException();
                }
            }
            var refName = method.First["returns"]["$ref"].ToString();
            refName = Global.GetRefName(refName, replacements);
            return new ReturnHandler.ReturnHandler(refName);
        }

        private static List<string> GenTypes(JToken types, Dictionary<string, string> replacements)
        {
            var multiBaseTypes = new List<string>();

            foreach (JProperty type in types)
            {
                var typeHandler = GetTypeHandler(type);

                var parts = type.First["id"].ToString().Split('.');
                var subPath = Path.Combine(parts.Take(parts.Length - 1).ToArray());
                var namesp = Global.BaseNamespace + "." + string.Join(".", parts.Take(parts.Length - 1));
                namesp = namesp.TrimEnd('.');
                var path = Path.Combine(Global.TargetDir.FullName, subPath);
                var className = parts.Last();
                var classDirAsFile = new FileInfo(path + ".cs");

                if (classDirAsFile.Exists)
                {
                    var lines = File.ReadAllLines(classDirAsFile.FullName);
                    File.WriteAllLines(classDirAsFile.FullName, lines.Take(lines.Length - 2));
                    var fileStream = classDirAsFile.Open(FileMode.Append, FileAccess.Write);
                    var writer = new StreamWriter(fileStream);
                    typeHandler.Write(writer, null, className, replacements);
                    writer.WriteLine("    }");
                    writer.WriteLine("}");
                    writer.Close();
                }
                else
                {
                    var classDir = new DirectoryInfo(path);
                    if (!classDir.Exists)
                    {
                        classDir.Create();
                    }

                    var classFile = new FileInfo(Path.Combine(path, className + ".cs"));
                    var writer = new StreamWriter(classFile.Open(FileMode.Create, FileAccess.Write));
                    typeHandler.Write(writer, namesp, className, replacements);
                    writer.Close();
                }

                if(typeHandler is MultiBaseType)
                {
                    multiBaseTypes.Add(namesp + "."+ className);
                }
            }
            return multiBaseTypes;
        }

        private static TypeHandler.TypeHandler GetTypeHandler(JProperty type)
        {
            var baseName = type.First["extends"];
            if(baseName!=null)
            {
                if (baseName.ToString() == "Item.Fields.Base")
                {
                    return new EnumArrayTypeHandler(type.First["items"]["enums"]);
                }
                if(baseName.ToString().StartsWith("["))
                {
                    
                    var typeHandler= new MultiBaseType(type.First["properties"], baseName);
                    Global.MultiBaseTypes.Add(type.Name, typeHandler);
                    return typeHandler;
                }     
                if(Global.MultiBaseTypes.ContainsKey(baseName.ToString()))
                {
                    var typeHandler = new MultiBaseType(type.First["properties"], Global.MultiBaseTypes[baseName.ToString()]);
                    Global.MultiBaseTypes.Add(type.Name, typeHandler);
                    return typeHandler;
                }
                return new ObjectTypeHandler(type.First["properties"], baseName.ToString());
            }
            var typeName = type.First["type"].ToString();
            switch (typeName)
            {
                case "string":
                    if (type.First["enums"]!=null)
                    {
                        return new EnumTypeHandler(type.First["enums"]);
                    }
                    return new StringTypeHandler();
                case "array":
                    return new ArrayTypeHandler(type.First["items"]);
                case "object":
                    return new ObjectTypeHandler(type.First["properties"]);
                default:
                    if(typeName.StartsWith("[") && !type.Name.StartsWith("Optional."))
                    {
                        var types=type.First["type"];
                        if (types.All(Global.IsValueType))
                        {
                            return new MultiValueHandler(types);
                            
                        }
                        else
                        {
                            return new MultiTypeHandler(types);
                            
                        }
                    }
                    
                    return new TypeHandler.TypeHandler();
            }
        }
    }
}






